package com.dianshang.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.dianshang.dao.BrandDao;
import com.dianshang.dao.SkuDao;
import com.dianshang.dao.SpecDao;
import com.dianshang.entity.PageResult;
import com.dianshang.pojo.goods.Brand;
import com.dianshang.pojo.goods.Sku;
import com.dianshang.service.goods.SkuService;
import com.dianshang.utils.ESClient;
import com.dianshang.utils.RedisClient;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.IOException;
import java.util.*;

@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuDao skuDao;

    @Autowired
    private RedisClient redisClient; // Redis的客户端工具类

    @Autowired
    private ESClient esClient; // ES的客户端对象

    @Autowired
    private BrandDao brandDao;

    @Autowired
    private SpecDao specDao;

    /**
     * 查询所有
     */
    @Override
    public List<Sku> findAll() {
        return skuDao.selectAll();
    }

    /**
     * 查询所有+分页
     * page 页码
     * size 每页记录数
     * 分页结果
     */
    @Override
    public PageResult<Sku> findPage(Integer page, Integer size) {
        PageHelper.startPage(page,size);
        Page<Sku> skus = (Page<Sku>) skuDao.selectAll();
        return new PageResult<Sku>(skus.getTotal(),skus.getResult());
    }

    /**
     * 查询所有+模糊
     * searchMap 查询条件
     */
    @Override
    public List<Sku> findList(Map<String, Object> searchMap) {
        Example example = createExample(searchMap);
        return skuDao.selectByExample(example);
    }

    /**
     * 查询所有+分页+模糊
     * searchMap
     * page
     * size
     */
    @Override
    public PageResult<Sku> findPage(Map<String, Object> searchMap, Integer page, Integer size) {
        PageHelper.startPage(page,size);
        Example example = createExample(searchMap);
        Page<Sku> skus = (Page<Sku>) skuDao.selectByExample(example);
        return new PageResult<Sku>(skus.getTotal(),skus.getResult());
    }

    /**
     * 回显
     * id
     */
    @Override
    public Sku findById(Integer id) {
        return skuDao.selectByPrimaryKey(id);
    }

    /**
     * 新增
     * sku
     */
    @Override
    public void add(Sku sku) {
        skuDao.insert(sku);
    }

    /**
     * 修改
     * sku
     */
    @Override
    public void update(Sku sku) {
        skuDao.updateByPrimaryKeySelective(sku);
    }

    /**
     * 删除
     * id
     */
    @Override
    public void delete(Integer id) {
        skuDao.deleteByPrimaryKey(id);
    }

    /**
     * 将商品价格加载到缓存中
     */
    @Override
    public Integer findPriceByIdRedis(String skuId) {
        // 先判断Redis里面是否有数据
        Integer price =(Integer) redisClient.get(skuId);
        if (price != null){
            return price; // 有的话直接返回
        }
        // 没有的话去数据库中查询
        Sku sku = skuDao.selectByPrimaryKey(skuId);
        price = sku.getPrice();
        // 加载到缓存
        redisClient.set(skuId,price);
        // 返回给页面
        return price;
    }

    /**
     * 一键导入数据到ES
     * PUT /yuchen
     *         {
     *         "mappings": {
     *         "doc": {
     *         "properties": {
     *         "name": {
     *         "type": "text",
     *         "analyzer": "ik_smart"
     *         },
     *         "price": {
     *         "type": "integer"
     *         },
     *         "image": {
     *         "type": "text"
     *         },
     *         "createTime": {
     *         "type": "date"
     *         },
     *         "spuId": {
     *         "type": "text"
     *         },
     *         "categoryName": {
     *         "type": "keyword"
     *         },
     *         "brandName": {
     *         "type": "keyword"
     *         },
     *         "spec": {
     *         "type": "object"
     *         },
     *         "saleNum": {
     *         "type": "integer"
     *         },
     *         "commentNum": {
     *         "type": "integer"
     *         }
     *       }
     *     }
     *   }
     * }
     */
    @Override
    public Integer importES() {
        int status = 0;
        try {
            // 查询SKU数据
            Map skuMap = new HashMap();
            skuMap.put("status","1");
            PageResult page = findPage(skuMap, 1, 100000);
            List<Sku> skuList = page.getRows();
            // 1、创建一个restClient客户端,用于访问ES
            // 方法一(原生方法)：
            HttpHost httpHost = new HttpHost("127.0.0.1",9200,"http"); // hostname：本机地址,port：端口号,scheme：HTTP协议
            RestClientBuilder restClientBuilder = RestClient.builder(httpHost); // rest客户端构建器
            RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder); // rest高级客户端
            // 方法二(配置方法)：
            // RestHighLevelClient restHighLevelClient = esClient.getRestHighLevelClient();
            BulkRequest bulkRequest = new BulkRequest(); // BulkRequest 循环批量导入
            for (Sku sku : skuList) { // 循环遍历符合要求的所有数据
                // 2、创建一个对应行为的请求对象
                IndexRequest indexRequest = new IndexRequest("yuchen","doc",sku.getId()); // 获取索引库名和类型名称
                Map map = new HashMap();
                map.put("name",sku.getName());
                map.put("price",sku.getPrice());
                map.put("image",sku.getImage());
                map.put("createTime",sku.getCreateTime());
                map.put("spuId",sku.getSpuId());
                map.put("categoryName",sku.getCategoryName());
                map.put("brandName",sku.getBrandName());
                // 重构spec对象,将spec对象转换成json
                Map specMap = JSON.parseObject(sku.getSpec());
                map.put("spec",specMap);
                map.put("saleNum",sku.getSaleNum());
                map.put("commentNum",sku.getCommentNum());
                indexRequest.source(map);
                bulkRequest.add(indexRequest);
            }
            // 3、触发请求,获取响应结果
            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT); // 触发请求,获取响应结果
            // 4、使用响应的结果
            status = bulkResponse.status().getStatus();
            String message = bulkResponse.buildFailureMessage();
            System.out.println("批量导入结果响应：status："+status+";message="+message);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return status;
    }

    /**
     * 搜索引擎(ElasticSearch)：
     * GET /yuchen/doc/_search
     * {
     *   "query": {
     *     "bool": {
     *       "must": [
     *         {
     *           "match": {
     *           "name": "手机" // 关键字
     *           }
     *         },
     *         {
     *           "term": {
     *               "categoryName": "手机" // 分类
     *           }
     *         },
     *         {
     *           "term": {
     *               "brandName": "华为" // 品牌
     *           }
     *         },
     *         {
     *           "term": {
     *               "spec.颜色.keyword": "红色" // 规格
     *           }
     *         }
     *       ],
     *       "filter": {
     *         "range": {
     *           "price": { // 价格
     *             "gt": 50000,
     *             "lte": 100000
     *           }
     *         }
     *       }
     *     }
     *   },
     *   "aggs": {
     *     "yuchen_category": {
     *       "terms": {
     *         "field": "categoryName" // 分组
     *       }
     *     }
     *   },
     *   "from": 0, // 从第一条数据开始查询
     *   "size": 30 // 要查询30条数据
     *   "sort": [
     *     {
     *       "price": { // 根据价格排序
     *         "order": "desc" // 倒叙
     *       }
     *     }
     *   ],
     *   "highlight": { // 名称高亮显示
     *     "fields": {
     *       "name": { // <font color='red'>手机</font>
     *         "pre_tags": "<font color='red'>",
     *         "post_tags": "</font>"
     *       }
     *     }
     *   }
     * }
     */
    @Override
    public Map search(Map<String,String> searchMap) throws IOException {
        String searchKey = searchMap.get("searchKey"); // 输入搜索框关键字
        int pageSize = 30; // 每页30条数据
        String pageNo = searchMap.get("pageNo"); // 当前页

        // 封装查询结果
        Map result = new HashMap();
        /**
         * 搜索引擎(Elasticsearch)操作步骤
         *      第一步：创建一个restClient客户端,用于访问ES
         */
        HttpHost httpHost = new HttpHost("127.0.0.1", 9200,"http"); // hostname：本机地址,port：端口号,scheme：HTTP协议
        RestClientBuilder restClientBuilder = RestClient.builder(httpHost); // rest客户端构建器
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder); // rest高级客户端
        // RestHighLevelClient restHighLevelClient = esClient.getRestHighLevelClient();
        /**
         * 搜索引擎(Elasticsearch)操作步骤
         *      第二步：创建一个对应行为的请求对象
         */
        SearchRequest searchRequest = new SearchRequest("yuchen"); // 获取索引库名
        searchRequest.types("doc"); // 获取类型名称
        if (!StringUtils.isEmpty(searchKey)){ // 非空判断,如果查询的数据不为空的就执行以下操作,反之,终止
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); // 封装查询条件
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 布尔查询构建器

            // 一、1.1 根据关键字查询
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name",searchKey); // 获取查询条件
            boolQueryBuilder.must(matchQueryBuilder); // 用来封装查询条件

            String categoryName = searchMap.get("categoryName"); // 输入搜索分类名称
            // 三、3.1 根据分类名称过滤查询
            if (!StringUtils.isEmpty(categoryName)){ // 非空判断,如果查询的数据不为空的就执行以下操作,反之,终止
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("categoryName",categoryName);
                boolQueryBuilder.filter(termQueryBuilder);
            }

            String brandName = searchMap.get("brandName"); // 输入搜索品牌名称
            // 四、4.2 根据品牌名称过滤查询
            if (!StringUtils.isEmpty(brandName)){ // 非空判断,如果查询的数据不为空的就执行以下操作,反之,终止
                TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName",brandName);
                boolQueryBuilder.filter(termQueryBuilder);
            }

            // 五、5.2 根据规格过滤查询
            // "spec.颜色.keyword": "红色"
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec.")){
                    // key = spec.颜色
                    String options = searchMap.get(key);
                    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(key+".keyword",options);
                    boolQueryBuilder.filter(termQueryBuilder);
                }
            }

            String price = searchMap.get("price"); // 输入搜索价格区间
            // 六、6.1 根据价格区间过滤查询
            if (!StringUtils.isEmpty(price)){ // 非空判断,如果查询的数据不为空的就执行以下操作,反之,终止
                String[] prices = price.split("-"); // 0-500元、500-1000元、1000-1500元、1500-2000元、2000-3000元、3000元-*
                RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");
                if (!prices[0].equals("*")) { // 如果价格由上限
                    rangeQueryBuilder.gte(prices[0] + "00"); // gte：大于 price[0](500-1000元)：500
                }
                if (!prices[1].equals("*")){ // 如果价格由上限   
                    rangeQueryBuilder.lte(prices[1] + "00"); // lte：小于 price[1](500-1000元)：1000
                }
                boolQueryBuilder.filter(rangeQueryBuilder);
            }

            // 把所有布尔查询的条件进行封装
            searchSourceBuilder.query(boolQueryBuilder);

            // 二、2.1 根据分类进行分组查询,得到分类名称
            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("yuchen_category").field("categoryName"); // 获取查询条件
            searchSourceBuilder.aggregation(termsAggregationBuilder); // 用来封装查询条件

            // 七、7.1 添加分页
            /*
             from：从当前数据位置开始查询
             size：要查询多少条数据
             pageNo         index
             1              0   (pageNo-1)*30
             2              30
             3              60
            */
            int index = 0;
            if (!StringUtils.isEmpty(pageNo)){
                index = (Integer.parseInt(pageNo)-1)*pageSize;
            }
            searchSourceBuilder.from(index); // 从index开始查询
            searchSourceBuilder.size(pageSize); // 要查询pageSize条数据

            // 八、8.1 添加排序方式
            String sort = searchMap.get("sort"); // 价格price、销量saleNum、评论数commentNum、创建时间createTime
            String order = searchMap.get("sortOrder"); // 升序ASC、倒叙DESC
            SortOrder sortOrder = SortOrder.DESC; // 倒叙
            sortOrder = "asc".equals(order)?SortOrder.ASC:sortOrder;
            if(!StringUtils.isEmpty(sort)){
                searchSourceBuilder.sort(sort, sortOrder);
            }

            // 九、9.1 名称高亮显示
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("name").preTags("<font color='red'>").postTags("</font>"); // <font color='red'>手机</font>
            searchSourceBuilder.highlighter(highlightBuilder);

            searchRequest.source(searchSourceBuilder); // 存入所有封装后的查询条件/设置查询条件构造器
        }
        /**
         * 搜索引擎(Elasticsearch)操作步骤
         *      第三步：触发请求,获取响应结果
         */
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        /**
         * 搜索引擎(Elasticsearch)操作步骤
         *      第四步：使用响应的结果
         */
        // 一、1.2 封装关键字信息
        SearchHits searchHits = searchResponse.getHits(); // 获取所有符合要求的数据
        long totalHits = searchHits.getTotalHits(); // 获取所有符合要求的条数
        System.out.println("记录数："+totalHits);
        if (totalHits > 0){ // 查询出的结果数据量大于0展示
            SearchHit[] hits = searchHits.getHits(); // 展示符合要求的所有数据
            List<Map<String, Object>> sourceAsMapList = new ArrayList<>(); // 多个Map对象：key:value
            for (SearchHit hit : hits) { // 循环遍历符合要求的所有数据
                Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 当前记录,单个Map对象：key:value

                String id = hit.getId();
                sourceAsMap.put("id",id); // id

                // 九、9.2 处理名称高亮显示
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                HighlightField name = highlightFields.get("name");
                Text[] texts = name.fragments();
                System.out.println(texts[0].toString());
                sourceAsMap.put("name",texts[0].toString()); // 用高亮的内容覆盖原有的名称内容

                sourceAsMapList.add(sourceAsMap);
            }
            result.put("sourceAsMapList",sourceAsMapList);

            // 二、2.2 封装分类名称信息
            Aggregations aggregations = searchResponse.getAggregations(); // 获取所有符合要求的数据
            Map<String, Aggregation> map = aggregations.asMap(); // 获取所有符合要求的条数
            Terms terms =(Terms) map.get("yuchen_category"); // 展示符合要求的所有数据
            List<? extends Terms.Bucket> list = terms.getBuckets();
            List<String> categoryList = new ArrayList<>();
            for (Terms.Bucket bucket : list) { // 循环遍历符合要求的所有数据
                String categoryName = bucket.getKeyAsString();
                System.out.println("分类名称："+categoryName);
                categoryList.add(categoryName);
            }
            result.put("categoryList",categoryList);

            // 方法一(默认使用第一个分类名称的品牌信息)：
            /* String categoryName = "";
            if (searchMap.get("category") == null){ // 如果没有分类条件
                if (categoryList.size()>0){
                    categoryName = categoryList.get(0); // 提取分类列表的第一个分类
                }
            } */
            // 方法二(默认使用第一个分类名称的品牌信息)：
            String categoryName = searchMap.get("categoryName");
            if(StringUtils.isEmpty(categoryName)){ // 如果没有分类条件
                categoryName = categoryList.get(0); // 提取分类列表的第一个分类
            }

            // 四、4.1 封装品牌信息：根据当前选择的分类名称查询品牌(默认使用第一个分类的品牌)
            List<Brand> brandList = brandDao.findBrand(categoryName); // 查询品牌列表(子查询+排序)
            result.put("brandList",brandList);

            // 五、5.1 封装规格信息：根据当前分类名称查询规格(默认使用第一个分类的规格)
            List<Map> specList = specDao.findSpec(categoryName); // 查询规格列表(子查询+排序)
            for (Map spec : specList) { // 循环遍历符合要求的所有数据
                // 红色,蓝色,黑色,槟色,白色,金色,银色,灰色.紫色
                String option =(String) spec.get("options");
                String[] options = option.split(",");
                spec.put("options",options);
            }
            result.put("specList",specList);

            // 七、7.2 封装页码  totalHits：全部数据,pageSize：每页30条数据
            long pages = totalHits%pageSize == 0?totalHits/pageSize:(totalHits/pageSize+1); // 总页数
            long start = 1; // 起始页码
            long end = 5; // 结束页码
            if (pages <= 5){
                end = pages;
            }else {
                if (!StringUtils.isEmpty(pageNo)){
                    start = Integer.parseInt(pageNo) - 2 <= 0?1:Integer.parseInt(pageNo) - 2;
                    if (Integer.parseInt(pageNo)>3){
                        end = Integer.parseInt(pageNo) + 2 >= pages?pages:Integer.parseInt(pageNo) + 2;
                    }
                }
            }
            result.put("start",start);
            result.put("end",end);
            result.put("totalPages",pages);
        }else {
            throw new RuntimeException("没有查询到符合到该条件的数据");
        }

        return result;
    }

    /**
     * 构建查询条件
     * searchMap
     */
    private Example createExample(Map<String, Object> searchMap){
        Example example=new Example(Sku.class);
        Example.Criteria criteria = example.createCriteria();
        if(searchMap != null){
            // 商品id
            if(searchMap.get("id")!=null && !"".equals(searchMap.get("id"))){
                criteria.andLike("id","%"+searchMap.get("id")+"%");
            }
            // 商品条码
            if(searchMap.get("sn")!=null && !"".equals(searchMap.get("sn"))){
                criteria.andLike("sn","%"+searchMap.get("sn")+"%");
            }
            // SKU名称
            if(searchMap.get("name")!=null && !"".equals(searchMap.get("name"))){
                criteria.andLike("name","%"+searchMap.get("name")+"%");
            }
            // 商品图片
            if(searchMap.get("image")!=null && !"".equals(searchMap.get("image"))){
                criteria.andLike("image","%"+searchMap.get("image")+"%");
            }
            // 商品图片列表
            if(searchMap.get("images")!=null && !"".equals(searchMap.get("images"))){
                criteria.andLike("images","%"+searchMap.get("images")+"%");
            }
            // SPUID
            if(searchMap.get("spuId")!=null && !"".equals(searchMap.get("spuId"))){
                criteria.andLike("spuId","%"+searchMap.get("spuId")+"%");
            }
            // 类目名称
            if(searchMap.get("categoryName")!=null && !"".equals(searchMap.get("categoryName"))){
                criteria.andLike("categoryName","%"+searchMap.get("categoryName")+"%");
            }
            // 品牌名称
            if(searchMap.get("brandName")!=null && !"".equals(searchMap.get("brandName"))){
                criteria.andLike("brandName","%"+searchMap.get("brandName")+"%");
            }
            // 规格
            if(searchMap.get("spec")!=null && !"".equals(searchMap.get("spec"))){
                criteria.andLike("spec","%"+searchMap.get("spec")+"%");
            }
            // 商品状态 1-正常,2-下架,3-删除
            if(searchMap.get("status")!=null && !"".equals(searchMap.get("status"))){
                criteria.andLike("status","%"+searchMap.get("status")+"%");
            }
            // 价格（分）
            if(searchMap.get("price")!=null ){
                criteria.andEqualTo("price",searchMap.get("price"));
            }
            // 库存数量
            if(searchMap.get("num")!=null ){
                criteria.andEqualTo("num",searchMap.get("num"));
            }
            // 库存预警数量
            if(searchMap.get("alertNum")!=null ){
                criteria.andEqualTo("alertNum",searchMap.get("alertNum"));
            }
            // 重量（克）
            if(searchMap.get("weight")!=null ){
                criteria.andEqualTo("weight",searchMap.get("weight"));
            }
            // 类目ID
            if(searchMap.get("categoryId")!=null ){
                criteria.andEqualTo("categoryId",searchMap.get("categoryId"));
            }
            // 销量
            if(searchMap.get("saleNum")!=null ){
                criteria.andEqualTo("saleNum",searchMap.get("saleNum"));
            }
            // 评论数
            if(searchMap.get("commentNum")!=null ){
                criteria.andEqualTo("commentNum",searchMap.get("commentNum"));
            }
        }
        return example;
    }

}